package xyz.lilei23.framework.bean.parser;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import xyz.lilei23.framework.annotation.Component;
import xyz.lilei23.framework.annotation.Service;
import xyz.lilei23.framework.bean.GenericBeanDefinition;
import xyz.lilei23.framework.utils.ClassHelper;
import xyz.lilei23.framework.utils.CollectionUtils;
import xyz.lilei23.framework.utils.FileHelper;

import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * xml bean配置解释器
 * @author jack
 * @date 31/07/2021 16:20
 * @version 1.0
 **/
public class XmlBeanConfigParser implements BeanConfigParser {

    private ConcurrentHashMap<String, GenericBeanDefinition> initDefinitionMap = new ConcurrentHashMap<>(16);


    @Override
    public List<GenericBeanDefinition> parse(InputStream inputStream) {
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(inputStream);
            Element rootElement = document.getRootElement();
            List<GenericBeanDefinition> transactionBeans = transactionParse(rootElement);
            List<GenericBeanDefinition> scanBeans = configScanParse(rootElement);
            List<GenericBeanDefinition> genericBeanDefinitions = beansParse(rootElement);
            if (scanBeans == null) {
                scanBeans = new ArrayList<>();
            }
            scanBeans.addAll(genericBeanDefinitions);
            scanBeans.addAll(transactionBeans);
            return scanBeans;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("类定义解析异常: {}" + e);
        }
    }

    private List<GenericBeanDefinition> transactionParse(Element rootElement) {
        List<Element> list = rootElement.selectNodes("//transaction");
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>(0);
        }
        List<Element> dataSourceProperties = rootElement.selectNodes(".//bean[contains(@class, 'xyz.lilei23.framework.jdbc.DataSourceProperties')]");
        List<Element> manager = rootElement.selectNodes("//bean[contains(@id, 'transactionManager')]");
        if (CollectionUtils.isEmpty(list) || CollectionUtils.isEmpty(manager)){
            throw new RuntimeException("事务核心类未配置");
        }
        dataSourceProperties.addAll(manager);
        List<Element> propertyList = rootElement.selectNodes("//property[c]");
        Map<String, List<GenericBeanDefinition.PropertyArg>> propertiesMap = getPropertyMap(propertyList);
        return dataSourceProperties.stream().map(element -> buildGenericBeanDefinition(propertiesMap, element)).collect(Collectors.toList());
    }

    private Map<String, List<GenericBeanDefinition.PropertyArg>> getPropertyMap(List<Element> propertyList) {
        return Optional.ofNullable(propertyList).map(pros -> pros.stream().map(this::buildPropertyArg).collect(Collectors.groupingBy(GenericBeanDefinition.PropertyArg::getParentId))).orElse(new HashMap<>(0));
    }

    private List<GenericBeanDefinition> beansParse(Element rootElement) throws Exception {
        List<Element> beanList = rootElement.selectNodes("//bean");
        if (beanList == null || beanList.isEmpty()) {
            return new ArrayList<>(0);
        }
        List<Element> propertyList = rootElement.selectNodes("//property");
        Map<String, List<GenericBeanDefinition.PropertyArg>> propertiesMap = getPropertyMap(propertyList);
        return beanList.stream().map(element ->
                buildGenericBeanDefinition(propertiesMap, element)).collect(Collectors.toList());
    }

    private GenericBeanDefinition buildGenericBeanDefinition(Map<String, List<GenericBeanDefinition.PropertyArg>> propertiesMap, Element element) {
        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        String aClass = element.attributeValue("class");
        genericBeanDefinition.setClassName(aClass);
        genericBeanDefinition.setId(element.attributeValue("id"));
        try {
            Class<?> bdClass = Class.forName(aClass);
            genericBeanDefinition.setBeanClass(bdClass);
            genericBeanDefinition.setArgs(ClassHelper.getConstructorArg(bdClass));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("类全名不存在");
        }
        String lazyInit = element.attributeValue("lazyInit");
        if (lazyInit != null) {
            genericBeanDefinition.setLazyInit(Boolean.getBoolean(lazyInit));
        }
        String scope = element.attributeValue("scope");
        if (scope != null) {
            genericBeanDefinition.setScope(GenericBeanDefinition.Scope.valueOf(scope));
        }
        if (!propertiesMap.isEmpty()) {
            genericBeanDefinition.setPropertiesArgs(propertiesMap.get(genericBeanDefinition.getId()));
        }
        return genericBeanDefinition;
    }

    private GenericBeanDefinition.PropertyArg buildPropertyArg(Element pro) {
        GenericBeanDefinition.PropertyArg propertyArg = new GenericBeanDefinition.PropertyArg();
        String name = pro.attributeValue("name");
        String ref = pro.attributeValue("ref");
        String value = pro.attributeValue("value");
        // 找到当前需要被处理依赖关系的bean
        Element parent = pro.getParent();
        // 调用父元素对象的反射功能
        String parentId = parent.attributeValue("id");
        propertyArg.setRef(ref);
        propertyArg.setName(name);
        propertyArg.setValue(value);
        propertyArg.setParentId(parentId);
        return propertyArg;
    }


    private List<GenericBeanDefinition> configScanParse(Element rootElement) {
        List<Element> list = rootElement.selectNodes("//config-scan");
        return Optional.ofNullable(list).map(
                elements -> elements.stream().flatMap(element -> {
                    String path = element.attributeValue("base-package");
                    if (path == null) {
                        throw new RuntimeException("配置文件解析异常");
                    }
                    return new FileHelper().findClassByPath(path).stream();
                }).map(path -> {
                    Class<?> bdClass;
                    try {
                        bdClass = Class.forName(path);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                        throw new RuntimeException("类路径解析失败:{}",e);
                    }
                    Component annotation = getComponent(bdClass, Component.class);
                    if (annotation==null){
                        return null;
                    }
                    String beanId;
                    if (!"".equals(annotation.value())) {
                        beanId = annotation.value();
                    } else {
                        beanId = path.substring(path.lastIndexOf(".")+1, path.lastIndexOf(".") + 2).toLowerCase() + path.substring(path.lastIndexOf(".") + 2);
                    }
                    beforeInitBeanDefinition(bdClass, annotation, beanId);
                    GenericBeanDefinition genericBeanDefinition = doInitBeanDefinition(path, bdClass, beanId);
                    afterInitBeanDefinition(genericBeanDefinition);
                    return genericBeanDefinition;
                }).filter(Objects::nonNull).collect(Collectors.toList())
        ).orElse(new ArrayList<>(0));
    }

    private GenericBeanDefinition doInitBeanDefinition(String path, Class<?> bdClass, String beanId) {
        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setId(beanId);
        genericBeanDefinition.setBeanClass(bdClass);
        genericBeanDefinition.setClassName(path);
        genericBeanDefinition.setArgs(ClassHelper.getConstructorArg(genericBeanDefinition.getBeanClass()));
        return genericBeanDefinition;
    }

    private void beforeInitBeanDefinition(Class<?> bdClass, Component annotation, String beanId) {
        GenericBeanDefinition oldBd;
        if ((oldBd = initDefinitionMap.get(annotation.value())) != null) {
            if (!oldBd.getBeanClass().equals(bdClass)) {
                throw new RuntimeException("存在重复bean id" + oldBd.getClassName() + "," + beanId);
            }
        }
    }

    private void afterInitBeanDefinition(GenericBeanDefinition genericBeanDefinition) {
        initDefinitionMap.put(genericBeanDefinition.getId(), genericBeanDefinition);
    }


    private Component getComponent(Class<?> bdClass, Class<Component> serviceClass) {
        Component annotation = bdClass.getAnnotation(serviceClass);
        if (annotation != null){
            return annotation;
        }
        for (Annotation classAnnotation : bdClass.getAnnotations()) {
            Component service = classAnnotation.annotationType().getAnnotation(serviceClass);
            if (service != null){
                try {
                    Method valueMethod = classAnnotation.annotationType().getMethod("value");
                    Object invoke = valueMethod.invoke(classAnnotation);
                    if (invoke == null || "".equals(invoke)){
                        return service;
                    }
                    InvocationHandler invocationHandler = Proxy.getInvocationHandler(service);
                    // 获取 AnnotationInvocationHandler 的 memberValues 字段
                    Field declaredField = invocationHandler.getClass().getDeclaredField("memberValues");
                    // 因为这个字段事 private final 修饰，所以要打开权限
                    declaredField.setAccessible(true);
                    // 获取 memberValues
                    Map<String, Object> memberValues = (Map<String, Object>) declaredField.get(invocationHandler);
                    // 修改 value 属性值
                    memberValues.put("value",  String.valueOf(invoke));
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("反射注解异常", e);
                }
                return service;
            }
        }
        return null;
    }
}
